package com.gzc.just.play.last.war.centralserver.inventory;



import com.gzc.just.play.last.war.centralserver.inventory.model.Inventory;

import com.gzc.just.play.last.war.centralserver.inventory.model.Item;

import com.gzc.just.play.last.war.centralserver.inventory.model.ItemStack;

import com.gzc.just.play.last.war.centralserver.inventory.repository.InventoryRepository;

import com.gzc.just.play.last.war.centralserver.inventory.repository.ItemRepository;

import com.gzc.just.play.last.war.centralserver.inventory.repository.ItemStackRepository;

import lombok.RequiredArgsConstructor;

import lombok.extern.slf4j.Slf4j;

import org.springframework.stereotype.Service;

import org.springframework.transaction.annotation.Transactional;



import java.util.*;

import java.util.concurrent.CompletableFuture;

import java.util.concurrent.ConcurrentHashMap;

import java.util.concurrent.ExecutorService;

import java.util.concurrent.Executors;



@Slf4j

@Service("central-inventoryManager")
@RequiredArgsConstructor

public class InventoryManager {



    private final InventoryRepository inventoryRepository;

    private final ItemStackRepository itemStackRepository;

    private final ItemRepository itemRepository;

    

    private final Map<Long, Item> itemCache = new ConcurrentHashMap<>();

    

    private final Map<Long, Inventory> inventoryCache = new ConcurrentHashMap<>();

    

    private final ExecutorService executorService = Executors.newVirtualThreadPerTaskExecutor();

    

    @Transactional

    public void initializePlayerInventory(Long playerId) {

        try {

            Optional<Inventory> existingInventory = inventoryRepository.findByPlayerId(playerId);

            if (existingInventory.isPresent()) {

                log.warn("Inventory already exists for player: {}", playerId);

                return;

            }

            

            Inventory inventory = new Inventory();

            inventory.setPlayerId(playerId);

            inventory.setUsedSlots(0);

            inventory.setCreateTime(System.currentTimeMillis());

            

            inventory = inventoryRepository.save(inventory);

            inventoryCache.put(playerId, inventory);

            

            log.info("Initialized inventory for player: {}", playerId);

            

        } catch (Exception e) {

            log.error("Error initializing inventory for player: {}", playerId, e);

        }

    }

    

    public boolean hasItem(Long playerId, Long itemId, int quantity) {

        try {

            Inventory inventory = getPlayerInventory(playerId);

            if (inventory == null) {

                return false;

            }

            

            List<ItemStack> itemStacks = itemStackRepository.findByInventoryIdAndItemId(inventory.getId(), itemId);

            

            int totalQuantity = itemStacks.stream().mapToInt(ItemStack::getQuantity).sum();

            

            return totalQuantity >= quantity;

            

        } catch (Exception e) {

            log.error("Error checking item quantity for player: {}, item: {}", playerId, itemId, e);

            return false;

        }

    }

    

    @Transactional

    public boolean useItem(Long playerId, Long itemId, int quantity, Long targetId) {

        try {

            Item item = getItem(itemId);

            if (item == null) {

                log.warn("Item not found: {}", itemId);

                return false;

            }

            

            if (!item.isUsable()) {

                log.warn("Item is not usable: {}", itemId);

                return false;

            }

            

            if (!hasItem(playerId, itemId, quantity)) {

                log.warn("Player {} does not have enough of item {}: {}", playerId, itemId, quantity);

                return false;

            }

            

            Inventory inventory = getPlayerInventory(playerId);

            if (inventory == null) {

                return false;

            }

            

            List<ItemStack> itemStacks = itemStackRepository.findByInventoryIdAndItemId(inventory.getId(), itemId);

            

            int remainingQuantity = quantity;

            

            for (ItemStack itemStack : itemStacks) {

                if (remainingQuantity <= 0) {

                    break;

                }

                

                int stackQuantity = itemStack.getQuantity();

                if (stackQuantity <= remainingQuantity) {

                    remainingQuantity -= stackQuantity;

                    itemStackRepository.delete(itemStack);

                    

                    inventory.setUsedSlots(inventory.getUsedSlots() - 1);

                } else {

                    itemStack.setQuantity(stackQuantity - remainingQuantity);

                    itemStackRepository.save(itemStack);

                    remainingQuantity = 0;

                }

            }

            

            inventoryRepository.save(inventory);

            inventoryCache.put(playerId, inventory);

            

            executeItemEffect(playerId, itemId, quantity, targetId);

            

            log.info("Player {} used item {} (quantity: {})", playerId, itemId, quantity);

            return true;

            

        } catch (Exception e) {

            log.error("Error using item for player: {}, item: {}", playerId, itemId, e);

            return false;

        }

    }

    

    @Transactional

    public boolean addItem(Long playerId, Long itemId, int quantity) {

        try {

            Item item = getItem(itemId);

            if (item == null) {

                log.warn("Item not found: {}", itemId);

                return false;

            }

            

            Inventory inventory = getPlayerInventory(playerId);

            if (inventory == null) {

                return false;

            }

            

            if (inventory.getUsedSlots() >= inventory.getMaxSlots() && !canStackWithExisting(inventory.getId(), itemId)) {

                log.warn("Inventory is full for player: {}", playerId);

                return false;

            }

            

            if (item.isStackable()) {

                List<ItemStack> existingStacks = itemStackRepository.findByInventoryIdAndItemId(inventory.getId(), itemId);

                

                for (ItemStack itemStack : existingStacks) {

                    int maxStack = item.getMaxStack();

                    int currentStack = itemStack.getQuantity();

                    

                    if (currentStack < maxStack) {

                        int canAdd = Math.min(maxStack - currentStack, quantity);

                        itemStack.setQuantity(currentStack + canAdd);

                        itemStackRepository.save(itemStack);

                        

                        quantity -= canAdd;

                        if (quantity <= 0) {

                            break;

                        }

                    }

                }

            }

            

            while (quantity > 0 && inventory.getUsedSlots() < inventory.getMaxSlots()) {

                ItemStack newItemStack = new ItemStack();

                newItemStack.setInventoryId(inventory.getId());

                newItemStack.setItemId(itemId);

                newItemStack.setQuantity(Math.min(quantity, item.getMaxStack()));

                newItemStack.setCreateTime(System.currentTimeMillis());

                

                itemStackRepository.save(newItemStack);

                

                quantity -= newItemStack.getQuantity();

                inventory.setUsedSlots(inventory.getUsedSlots() + 1);

            }

            

            inventoryRepository.save(inventory);

            inventoryCache.put(playerId, inventory);

            

            return true;

            

        } catch (Exception e) {

            log.error("Error adding item to inventory for player: {}, item: {}", playerId, itemId, e);

            return false;

        }

    }

    

    @Transactional

    public boolean removeItem(Long playerId, Long itemId, int quantity) {

        try {

            if (!hasItem(playerId, itemId, quantity)) {

                return false;

            }

            

            Inventory inventory = getPlayerInventory(playerId);

            if (inventory == null) {

                return false;

            }

            

            List<ItemStack> itemStacks = itemStackRepository.findByInventoryIdAndItemId(inventory.getId(), itemId);

            

            int remainingQuantity = quantity;

            

            for (ItemStack itemStack : itemStacks) {

                if (remainingQuantity <= 0) {

                    break;

                }

                

                int stackQuantity = itemStack.getQuantity();

                if (stackQuantity <= remainingQuantity) {

                    remainingQuantity -= stackQuantity;

                    itemStackRepository.delete(itemStack);

                    

                    inventory.setUsedSlots(inventory.getUsedSlots() - 1);

                } else {

                    itemStack.setQuantity(stackQuantity - remainingQuantity);

                    itemStackRepository.save(itemStack);

                    remainingQuantity = 0;

                }

            }

            

            inventoryRepository.save(inventory);

            inventoryCache.put(playerId, inventory);

            

            return true;

            

        } catch (Exception e) {

            log.error("Error removing item from inventory for player: {}, item: {}", playerId, itemId, e);

            return false;

        }

    }

    

    public Inventory getPlayerInventory(Long playerId) {

        Inventory inventory = inventoryCache.get(playerId);

        

        if (inventory == null) {

            Optional<Inventory> inventoryOptional = inventoryRepository.findByPlayerId(playerId);

            if (inventoryOptional.isPresent()) {

                inventory = inventoryOptional.get();

                inventoryCache.put(playerId, inventory);

            }

        }

        

        return inventory;

    }

    

    public CompletableFuture<List<ItemStack>> getPlayerItems(Long playerId) {

        return CompletableFuture.supplyAsync(() -> {

            try {

                Inventory inventory = getPlayerInventory(playerId);

                if (inventory == null) {

                    return Collections.emptyList();

                }

                

                return itemStackRepository.findByInventoryId(inventory.getId());

                

            } catch (Exception e) {

                log.error("Error getting player items: {}", playerId, e);

                return Collections.emptyList();

            }

        }, executorService);

    }

    

    private void executeItemEffect(Long playerId, Long itemId, int quantity, Long targetId) {

        Item item = getItem(itemId);

        if (item == null) {

            return;

        }

        

        switch (item.getType()) {

            case "HEALTH_POTION":

                break;

            case "EXPERIENCE_POTION":

                break;

            case "GOLD_BOX":

                break;

            default:

                log.debug("No special effect for item type: {}", item.getType());

        }

    }

    

    private Item getItem(Long itemId) {

        Item item = itemCache.get(itemId);

        

        if (item == null) {

            Optional<Item> itemOptional = itemRepository.findById(itemId);

            if (itemOptional.isPresent()) {

                item = itemOptional.get();

                itemCache.put(itemId, item);

            }

        }

        

        return item;

    }

    

    private boolean canStackWithExisting(Long inventoryId, Long itemId) {

        List<ItemStack> existingStacks = itemStackRepository.findByInventoryIdAndItemId(inventoryId, itemId);

        if (existingStacks.isEmpty()) {

            return false;

        }

        

        Item item = getItem(itemId);

        if (item == null || !item.isStackable()) {

            return false;

        }

        

        return existingStacks.stream().anyMatch(stack -> 

            stack.getQuantity() < item.getMaxStack()

        );

    }

}

